// Copyright Gary Evans 2006.

using System;

using NUnit.Framework;

namespace Taumuon.Jabuka.Physics.Test
{
    /// <summary>
    /// Tests that are dimension specific.
    /// The contained test cases will be ran for the three
    /// dimensions.
    /// </summary>
    public class TestEulerRigidBodyNewtonLawsDimensionSpecific
    {
        #region Private Member Variables

        private int vectorAxis = 0;

        #endregion Private Member Variables

        #region Test Methods

        /// <summary>
        /// Tests that a body initially at rest remains at rest.
        /// </summary>
        [Test]
        public void TestRemainsAtRest()
        {
            EulerRigidBody body = CreateBody(0.0, 0.0);
            UpdateBodyAndCheckState(body, 0.0, 0.0, 0.0, 1);
        }

        /// <summary>
        /// Tests that a body with a non-zero initial velocity
        /// moves with a constant velocity.
        /// </summary>
        [Test]
        public void TestNoForceVelocityRemainsConstant()
        {
            EulerRigidBody body = CreateBody(0.0, 0.5);
            UpdateBodyAndCheckState(body, 0.5, 5.0, 0.0, 1);
        }

        /// <summary>
        /// Tests that a body subject to a constant applied
        /// force updates its location and velocity.
        /// </summary>
        [Test]
        public void TestForceVelocityAndLocationChange()
        {
            EulerRigidBody body = CreateBody(0.0, 0.0);
            UpdateBodyAndCheckState(body, 10.0, 50.0499999999,
                1.0, 1000);
        }

        /// <summary>
        /// Tests that a body subject to a constant applied
        /// force updates its location and velocity, with a mass
        /// of two kilograms.
        /// </summary>
        [Test]
        public void TestForceMassTwoKilograms()
        {
            EulerRigidBody body = CreateBody(0.0, 0.0, 2.0);
            UpdateBodyAndCheckState(body, 5.0, 25.0249999999,
                1.0, 1000);
        }

        /// <summary>
        /// Tests that a body subject to a constant applied
        /// force updates its location and velocity, with a mass
        /// of two kilograms, and non-zero initial velocity.
        /// </summary>
        [Test]
        public void TestMassTwoKilogramsNonZeroLocation()
        {
            EulerRigidBody body = CreateBody(-5.0, 0.0, 2.0);
            UpdateBodyAndCheckState(body, 5.0, 20.0249999999,
                1.0, 1000);
        }


        /// <summary>
        /// Tests that the Update method works if
        ///   ApplyForce is not called.
        /// </summary>
        [Test]
        public void TestSetNoForce()
        {
            EulerRigidBody body = CreateBody(0.0, 0.0);
            body.Update(0.1);
        }

        /// <summary>
        /// Tests that a body subject to a constant applied
        /// force updates its location and velocity, with a mass
        /// of two kilograms, and non-zero initial velocity.
        /// This test is against the Y dimension.
        /// </summary>
        // [Test]
        public void TestMassTwoKilogramsNonZeroLocationYDimension()
        {
            Vector location = new Vector(0.0, -5.0, 0.0);
            Vector velocity = new Vector(0.0, 0.0, 0.0);
            EulerRigidBody body = new EulerRigidBody(location, velocity, 2.0);

            double expectedVelocity = 5.0;
            double expectedLocation = 20.0249999999;
            double appliedForce = 1.0;
            int numberOfIterations = 1000;

            const double time = 10.0;
            body.ApplyForce(new Vector(0.0, appliedForce, 0.0));
            for (int i = 1; i <= numberOfIterations; ++i)
            {
                body.Update(time / numberOfIterations);
            }
            const double tolerance = 1e-10;
            Assert.AreEqual(expectedVelocity, body.Velocity.Y,
                tolerance, "Velocity not as expected");
            Assert.AreEqual(expectedLocation, body.Location.Y,
                tolerance, "Location not as expected");
        }

        #endregion Test Methods

        #region Helper Methods

        /// <summary>
        /// Retrieves the value stored in the current fixture's
        ///  dimension out of the provided Vector.
        /// </summary>
        private double GetVectorValue(Vector vector, int dimension)
        {
            switch (dimension)
            {
                case 0 :
                    return vector.X;
                case 1 :
                    return vector.Y;
                case 2 :
                    return vector.Z;
                default :
                    throw new ApplicationException
                        ("Unrecognised dimension: " + 
                        dimension.ToString());
             }
        }

        /// <summary>
        /// Stores the value into the Vector given fixture's
        /// current dimension.
        /// </summary>
        private void SetVectorValue(Vector vector, int dimension,
            double value)
        {
            switch (dimension)
            {
                case 0:
                    vector.X = value;
                    break;
                case 1:
                    vector.Y = value;
                    break;
                case 2:
                    vector.Z = value;
                    break;
                default:
                    throw new ApplicationException
                        ("Unrecognised dimension: " +
                        dimension.ToString());
            }
        }

        /// <summary>
        /// Creates a body with a default mass of 1kg.
        /// </summary>
        /// <param name="location">The initial location</param>
        /// <param name="velocity">The initial velocity</param>
        /// <returns>A new body</returns>
        private EulerRigidBody CreateBody(double location, double velocity)
        {
            return CreateBody(location, velocity, 1.0);
        }

        /// <summary>
        /// Creates a body.
        /// </summary>
        /// <param name="location">The initial location</param>
        /// <param name="velocity">The initial velocity</param>
        /// <param name="mass">The body's mass</param>
        /// <returns>A new body</returns>
        private EulerRigidBody CreateBody(double location,
            double velocity, double mass)
        {
            Vector locationVector = new Vector(0.0, 0.0, 0.0);
            SetVectorValue(locationVector, vectorAxis, location);
            Vector velocityVector = new Vector(0.0, 0.0, 0.0);
            SetVectorValue(velocityVector, vectorAxis, velocity);
            return new EulerRigidBody(locationVector, velocityVector, mass);
        }

        private void UpdateBodyAndCheckState(EulerRigidBody body,
            double expectedVelocity, double expectedLocation,
            double appliedForce, int numberOfIterations)
        {
            const double time = 10.0;
            Vector force = new Vector(0.0, 0.0, 0.0);
            SetVectorValue(force, vectorAxis, appliedForce);
            body.ApplyForce(force);
            for (int i = 1; i <= numberOfIterations; ++i)
            {
                body.Update(time / numberOfIterations);
            }
            const double tolerance = 1e-10;
            Assert.AreEqual(expectedVelocity,
                GetVectorValue(body.Velocity, vectorAxis),
                tolerance, "Velocity not as expected");
            Assert.AreEqual(expectedLocation,
                GetVectorValue(body.Location, vectorAxis),
                tolerance, "Location not as expected");
        }

        #endregion Helper Methods

        #region Protected Accessors

        /// <summary>
        /// Sets the current dimension for the test fixture.
        /// </summary>
        protected int VectorAxis
        {
            set
            {
                vectorAxis = value;
            }
        }

        #endregion Protected Accessors
    }
}
